#ifndef RAY_TRACE_PRO_RAY_H
#define RAY_TRACE_PRO_RAY_H

#include "VectorMath.h"

namespace RayTracePro
{
	using namespace GxLibMath;

	class Ray
	{
	public:
		Vec3 Origin;
		Vec3 Direction;
		Vec3 ReciprocalDirection;
		float tMin, tMax;
	};

	class RayPacket
	{
	public:
		int Count;
		float * OriginX;
		float * OriginY;
		float * OriginZ;
		float * DirectionX;
		float * DirectionY;
		float * DirectionZ;
		float * InvDirectionX;
		float * InvDirectionY;
		float * InvDirectionZ;
		float * tMin;
		float * tMax;
		RayPacket(int size)
		{
			Count = size;
			OriginX = new float[size*11];
			OriginY = OriginX + size;
			OriginZ = OriginY + size;
			DirectionX = OriginZ + size;
			DirectionY = DirectionX + size;
			DirectionZ = DirectionY + size;
			InvDirectionX = DirectionZ + size;
			InvDirectionY = InvDirectionX + size;
			InvDirectionZ = InvDirectionY + size;
			tMin = InvDirectionZ + size;
			tMax = tMin + size;
		}
		~RayPacket()
		{
			delete [] OriginX;
		}
		inline void SetOrigin(int i, const Vec3 & origin)
		{
#ifdef DEBUG
			if (i < 0 || i > Count)
				throw "Index out of range";
#endif
			OriginX[i] = origin.x;
			OriginY[i] = origin.y;
			OriginZ[i] = origin.z;
		}
		inline Vec3 GetOrigin(int i)
		{
#ifdef DEBUG
			if (i < 0 || i > Count)
				throw "Index out of range";
#endif
			Vec3 rs;
			rs.x = OriginX[i];
			rs.y = OriginY[i];
			rs.z = OriginZ[i];
			return rs;
		}
		inline void SetDirection(int i, const Vec3 & dir)
		{
#ifdef DEBUG
			if (i < 0 || i > Count)
				throw "Index out of range";
#endif
			DirectionX[i] = dir.x;
			DirectionY[i] = dir.y;
			DirectionZ[i] = dir.z;
			InvDirectionX[i] = 1.0f/dir.x;
			InvDirectionY[i] = 1.0f/dir.y;
			InvDirectionZ[i] = 1.0f/dir.z;
		}
		inline Vec3 GetDirection(int i)
		{
#ifdef DEBUG
			if (i < 0 || i > Count)
				throw "Index out of range";
#endif
			Vec3 rs;
			rs.x = DirectionX[i];
			rs.y = DirectionY[i];
			rs.z = DirectionZ[i];
			return rs;
		}
		inline Vec3 GetInvDirection(int i)
		{
#ifdef DEBUG
			if (i < 0 || i > Count)
				throw "Index out of range";
#endif
			Vec3 rs;
			rs.x = InvDirectionX[i];
			rs.y = InvDirectionY[i];
			rs.z = InvDirectionZ[i];
			return rs;
		}
	};

	class RayDifferential
	{
	public:
		Vec3 dPdx, dPdy, dDdx, dDdy;
	};

	class RayDifferentialPacket
	{
	public:
		int Count;
		float * dPdx_x;
		float * dPdx_y;
		float * dPdx_z;
		float * dPdy_x;
		float * dPdy_y;
		float * dPdy_z;
		float * dDdx_x;
		float * dDdx_y;
		float * dDdx_z;
		float * dDdy_x;
		float * dDdy_y;
		float * dDdy_z;
		RayDifferentialPacket(int size)
		{
			Count = size;
			dPdx_x = new float[size * 12];
			dPdx_y = dPdx_x + size;
			dPdx_z = dPdx_x + size * 2;
			dPdy_x = dPdx_x + size * 3;
			dPdy_y = dPdx_x + size * 4;
			dPdy_z = dPdx_x + size * 5;
			dDdx_x = dPdx_x + size * 6;
			dDdx_y = dPdx_x + size * 7;
			dDdx_z = dPdx_x + size * 8;
			dDdy_x = dPdx_x + size * 9;
			dDdy_y = dPdx_x + size * 10;
			dDdy_z = dPdx_x + size * 11;
		}
		~RayDifferentialPacket()
		{
			delete [] dPdx_x;
		}
		inline void Set_dPdx(int i, const Vec3 & dpdx)
		{
#ifdef DEBUG
			if (i < 0 || i > Count)
				throw "Index out of range";
#endif
			dPdx_x[i] = dpdx.x;
			dPdx_y[i] = dpdx.y;
			dPdx_z[i] = dpdx.z;
		}
		inline Vec3 Get_dPdx(int i)
		{
#ifdef DEBUG
			if (i < 0 || i > Count)
				throw "Index out of range";
#endif
			return Vec3(dPdx_x[i], dPdx_y[i], dPdx_z[i]);
		}
		inline void Set_dPdy(int i, const Vec3 & dpdy)
		{
#ifdef DEBUG
			if (i < 0 || i > Count)
				throw "Index out of range";
#endif
			dPdy_x[i] = dpdy.x;
			dPdy_y[i] = dpdy.y;
			dPdy_z[i] = dpdy.z;
		}
		inline Vec3 Get_dPdy(int i)
		{
#ifdef DEBUG
			if (i < 0 || i > Count)
				throw "Index out of range";
#endif
			return Vec3(dPdy_x[i], dPdy_y[i], dPdy_z[i]);
		}
		inline void Set_dDdx(int i, const Vec3 & dddx)
		{
#ifdef DEBUG
			if (i < 0 || i > Count)
				throw "Index out of range";
#endif
			dDdx_x[i] = dddx.x;
			dDdx_y[i] = dddx.y;
			dDdx_z[i] = dddx.z;
		}
		inline Vec3 Get_dDdx(int i)
		{
#ifdef DEBUG
			if (i < 0 || i > Count)
				throw "Index out of range";
#endif
			return Vec3(dDdx_x[i], dDdx_y[i], dDdx_z[i]);
		}
		inline void Set_dDdy(int i, const Vec3 & dddy)
		{
#ifdef DEBUG
			if (i < 0 || i > Count)
				throw "Index out of range";
#endif
			dDdy_x[i] = dddy.x;
			dDdy_y[i] = dddy.y;
			dDdy_z[i] = dddy.z;
		}
		inline Vec3 Get_dDdy(int i)
		{
#ifdef DEBUG
			if (i < 0 || i > Count)
				throw "Index out of range";
#endif
			return Vec3(dDdy_x[i], dDdy_y[i], dDdy_z[i]);
		}
	};

	inline void TransferRayDifferential(RayDifferential & rayDiff, const Ray & r, const Vec3 & normal, float t)
	{
		Vec3 tmpVec;
		float invDdotN = 1.0f/Vec3::Dot(r.Direction, normal);
		Vec3::Scale(tmpVec, rayDiff.dDdx, t);
		rayDiff.dPdx += tmpVec;
		float dtdx = -Vec3::Dot(rayDiff.dPdx, normal)*invDdotN;
		Vec3::Scale(tmpVec, r.Direction, dtdx);
		rayDiff.dPdx += tmpVec;

		Vec3::Scale(tmpVec, rayDiff.dDdy, t);
		rayDiff.dPdy += tmpVec;
		float dtdy = -Vec3::Dot(rayDiff.dPdy, normal)*invDdotN;
		Vec3::Scale(tmpVec, r.Direction, dtdy);
		rayDiff.dPdy += tmpVec;
	}
}

#endif